<?xml version="1.0" encoding="utf-8" standalone="no"?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN"
        "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">

<html xmlns="http://www.w3.org/1999/xhtml">
<head>
    <title></title>
    <link href="../Styles/sure3.css" rel="stylesheet" type="text/css"/>
    <link href="../Styles/base1.css" rel="stylesheet" type="text/css"/>
</head>

<body>
<div class="markdown-body entry-content" itemprop="text">
    <h1><a href="#generator-函数的异步应用"  class="anchor" id="user-content-generator-函数的异步应用">

    </a>Generator 函数的异步应用
    </h1>
    <p>异步编程对 JavaScript 语言太重要。Javascript 语言的执行环境是“单线程”的，如果没有异步编程，根本没法用，非卡死不可。本章主要介绍 Generator 函数如何完成异步操作。</p>
    <h2><a href="#传统方法"  class="anchor" id="user-content-传统方法">

    </a>传统方法
    </h2>
    <p>ES6 诞生以前，异步编程的方法，大概有下面四种。</p>
    <ul>
        <li>回调函数</li>
        <li>事件监听</li>
        <li>发布/订阅</li>
        <li>Promise 对象</li>
    </ul>
    <p>Generator 函数将 JavaScript 异步编程带入了一个全新的阶段。</p>
    <h2><a href="#基本概念"  class="anchor" id="user-content-基本概念">

    </a>基本概念
    </h2>
    <h3><a href="#异步"  class="anchor" id="user-content-异步">

    </a>异步
    </h3>
    <p>所谓"异步"，简单说就是一个任务不是连续完成的，可以理解成该任务被人为分成两段，先执行第一段，然后转而执行其他任务，等做好了准备，再回过头执行第二段。</p>
    <p>比如，有一个任务是读取文件进行处理，任务的第一段是向操作系统发出请求，要求读取文件。然后，程序执行其他任务，等到操作系统返回文件，再接着执行任务的第二段（处理文件）。这种不连续的执行，就叫做异步。</p>
    <p>相应地，连续的执行就叫做同步。由于是连续执行，不能插入其他任务，所以操作系统从硬盘读取文件的这段时间，程序只能干等着。</p>
    <h3><a href="#回调函数"  class="anchor" id="user-content-回调函数">

    </a>回调函数
    </h3>
    <p>JavaScript 语言对异步编程的实现，就是回调函数。所谓回调函数，就是把任务的第二段单独写在一个函数里面，等到重新执行这个任务的时候，就直接调用这个函数。回调函数的英语名字<code>callback</code>，直译过来就是"重新调用"。
    </p>
    <p>读取文件进行处理，是这样写的。</p>
    <div class="highlight highlight-source-js"><pre><span class="pl-smi">fs</span>.<span
            class="pl-en">readFile</span>(<span class="pl-s"><span class="pl-pds">'</span>/etc/passwd<span
            class="pl-pds">'</span></span>, <span class="pl-s"><span class="pl-pds">'</span>utf-8<span
            class="pl-pds">'</span></span>, <span class="pl-k">function</span> (<span class="pl-smi">err</span>, <span
            class="pl-smi">data</span>) {
  <span class="pl-k">if</span> (err) <span class="pl-k">throw</span> err;
  <span class="pl-en">console</span>.<span class="pl-c1">log</span>(data);
});</pre>
    </div>
    <p>上面代码中，<code>readFile</code>函数的第三个参数，就是回调函数，也就是任务的第二段。等到操作系统返回了<code>/etc/passwd</code>这个文件以后，回调函数才会执行。</p>
    <p>一个有趣的问题是，为什么 Node 约定，回调函数的第一个参数，必须是错误对象<code>err</code>（如果没有错误，该参数就是<code>null</code>）？</p>
    <p>原因是执行分成两段，第一段执行完以后，任务所在的上下文环境就已经结束了。在这以后抛出的错误，原来的上下文环境已经无法捕捉，只能当作参数，传入第二段。</p>
    <h3><a href="#promise"  class="anchor" id="user-content-promise">

    </a>Promise
    </h3>
    <p>回调函数本身并没有问题，它的问题出现在多个回调函数嵌套。假定读取<code>A</code>文件之后，再读取<code>B</code>文件，代码如下。</p>
    <div class="highlight highlight-source-js"><pre><span class="pl-smi">fs</span>.<span class="pl-en">readFile</span>(fileA, <span
            class="pl-s"><span class="pl-pds">'</span>utf-8<span class="pl-pds">'</span></span>, <span class="pl-k">function</span> (<span
            class="pl-smi">err</span>, <span class="pl-smi">data</span>) {
  <span class="pl-smi">fs</span>.<span class="pl-en">readFile</span>(fileB, <span class="pl-s"><span
                class="pl-pds">'</span>utf-8<span class="pl-pds">'</span></span>, <span
                class="pl-k">function</span> (<span class="pl-smi">err</span>, <span class="pl-smi">data</span>) {
    <span class="pl-c"><span class="pl-c">//</span> ...</span>
  });
});</pre>
    </div>
    <p>
        不难想象，如果依次读取两个以上的文件，就会出现多重嵌套。代码不是纵向发展，而是横向发展，很快就会乱成一团，无法管理。因为多个异步操作形成了强耦合，只要有一个操作需要修改，它的上层回调函数和下层回调函数，可能都要跟着修改。这种情况就称为"回调函数地狱"（callback
        hell）。</p>
    <p>Promise 对象就是为了解决这个问题而提出的。它不是新的语法功能，而是一种新的写法，允许将回调函数的嵌套，改成链式调用。采用 Promise，连续读取多个文件，写法如下。</p>
    <div class="highlight highlight-source-js"><pre><span class="pl-k">var</span> readFile <span
            class="pl-k">=</span> <span class="pl-c1">require</span>(<span class="pl-s"><span class="pl-pds">'</span>fs-readfile-promise<span
            class="pl-pds">'</span></span>);

<span class="pl-en">readFile</span>(fileA)
.<span class="pl-en">then</span>(<span class="pl-k">function</span> (<span class="pl-smi">data</span>) {
  <span class="pl-en">console</span>.<span class="pl-c1">log</span>(<span class="pl-smi">data</span>.<span
                class="pl-c1">toString</span>());
})
.<span class="pl-en">then</span>(<span class="pl-k">function</span> () {
  <span class="pl-k">return</span> <span class="pl-en">readFile</span>(fileB);
})
.<span class="pl-en">then</span>(<span class="pl-k">function</span> (<span class="pl-smi">data</span>) {
  <span class="pl-en">console</span>.<span class="pl-c1">log</span>(<span class="pl-smi">data</span>.<span
                class="pl-c1">toString</span>());
})
.<span class="pl-en">catch</span>(<span class="pl-k">function</span> (<span class="pl-smi">err</span>) {
  <span class="pl-en">console</span>.<span class="pl-c1">log</span>(err);
});</pre>
    </div>
    <p>上面代码中，我使用了<code>fs-readfile-promise</code>模块，它的作用就是返回一个 Promise 版本的<code>readFile</code>函数。Promise
        提供<code>then</code>方法加载回调函数，<code>catch</code>方法捕捉执行过程中抛出的错误。</p>
    <p>可以看到，Promise 的写法只是回调函数的改进，使用<code>then</code>方法以后，异步任务的两段执行看得更清楚了，除此以外，并无新意。</p>
    <p>Promise 的最大问题是代码冗余，原来的任务被 Promise 包装了一下，不管什么操作，一眼看去都是一堆<code>then</code>，原来的语义变得很不清楚。</p>
    <p>那么，有没有更好的写法呢？</p>
    <h2><a href="#generator-函数"  class="anchor" id="user-content-generator-函数">

    </a>Generator 函数
    </h2>
    <h3><a href="#协程"  class="anchor" id="user-content-协程">

    </a>协程
    </h3>
    <p>传统的编程语言，早有异步编程的解决方案（其实是多任务的解决方案）。其中有一种叫做"协程"（coroutine），意思是多个线程互相协作，完成异步任务。</p>
    <p>协程有点像函数，又有点像线程。它的运行流程大致如下。</p>
    <ul>
        <li>第一步，协程<code>A</code>开始执行。</li>
        <li>第二步，协程<code>A</code>执行到一半，进入暂停，执行权转移到协程<code>B</code>。</li>
        <li>第三步，（一段时间后）协程<code>B</code>交还执行权。</li>
        <li>第四步，协程<code>A</code>恢复执行。</li>
    </ul>
    <p>上面流程的协程<code>A</code>，就是异步任务，因为它分成两段（或多段）执行。</p>
    <p>举例来说，读取文件的协程写法如下。</p>
    <div class="highlight highlight-source-js"><pre><span class="pl-k">function</span><span class="pl-k">*</span> <span
            class="pl-en">asyncJob</span>() {
  <span class="pl-c"><span class="pl-c">//</span> ...其他代码</span>
  <span class="pl-k">var</span> f <span class="pl-k">=</span> <span class="pl-k">yield</span> <span class="pl-en">readFile</span>(fileA);
  <span class="pl-c"><span class="pl-c">//</span> ...其他代码</span>
}</pre>
    </div>
    <p>上面代码的函数<code>asyncJob</code>是一个协程，它的奥妙就在其中的<code>yield</code>命令。它表示执行到此处，执行权将交给其他协程。也就是说，<code>yield</code>命令是异步两个阶段的分界线。
    </p>
    <p>协程遇到<code>yield</code>命令就暂停，等到执行权返回，再从暂停的地方继续往后执行。它的最大优点，就是代码的写法非常像同步操作，如果去除<code>yield</code>命令，简直一模一样。</p>
    <h3><a href="#协程的-generator-函数实现"  class="anchor" id="user-content-协程的-generator-函数实现">

    </a>协程的 Generator 函数实现
    </h3>
    <p>Generator 函数是协程在 ES6 的实现，最大特点就是可以交出函数的执行权（即暂停执行）。</p>
    <p>整个 Generator 函数就是一个封装的异步任务，或者说是异步任务的容器。异步操作需要暂停的地方，都用<code>yield</code>语句注明。Generator 函数的执行方法如下。</p>
    <div class="highlight highlight-source-js"><pre><span class="pl-k">function</span><span class="pl-k">*</span> <span
            class="pl-en">gen</span>(<span class="pl-smi">x</span>) {
  <span class="pl-k">var</span> y <span class="pl-k">=</span> <span class="pl-k">yield</span> x <span
                class="pl-k">+</span> <span class="pl-c1">2</span>;
  <span class="pl-k">return</span> y;
}

<span class="pl-k">var</span> g <span class="pl-k">=</span> <span class="pl-en">gen</span>(<span class="pl-c1">1</span>);
<span class="pl-smi">g</span>.<span class="pl-c1">next</span>() <span class="pl-c"><span class="pl-c">//</span> { value: 3, done: false }</span>
<span class="pl-smi">g</span>.<span class="pl-c1">next</span>() <span class="pl-c"><span class="pl-c">//</span> { value: undefined, done: true }</span></pre>
    </div>
    <p>上面代码中，调用 Generator 函数，会返回一个内部指针（即遍历器）<code>g</code>。这是 Generator
        函数不同于普通函数的另一个地方，即执行它不会返回结果，返回的是指针对象。调用指针<code>g</code>的<code>next</code>方法，会移动内部指针（即执行异步任务的第一段），指向第一个遇到的<code>yield</code>语句，上例是执行到<code>x
            + 2</code>为止。</p>
    <p>
        换言之，<code>next</code>方法的作用是分阶段执行<code>Generator</code>函数。每次调用<code>next</code>方法，会返回一个对象，表示当前阶段的信息（<code>value</code>属性和<code>done</code>属性）。<code>value</code>属性是<code>yield</code>语句后面表达式的值，表示当前阶段的值；<code>done</code>属性是一个布尔值，表示
        Generator 函数是否执行完毕，即是否还有下一个阶段。</p>
    <h3><a href="#generator-函数的数据交换和错误处理"  class="anchor" id="user-content-generator-函数的数据交换和错误处理">

    </a>Generator 函数的数据交换和错误处理
    </h3>
    <p>Generator 函数可以暂停执行和恢复执行，这是它能封装异步任务的根本原因。除此之外，它还有两个特性，使它可以作为异步编程的完整解决方案：函数体内外的数据交换和错误处理机制。</p>
    <p><code>next</code>返回值的 value 属性，是 Generator 函数向外输出数据；<code>next</code>方法还可以接受参数，向 Generator 函数体内输入数据。</p>
    <div class="highlight highlight-source-js"><pre><span class="pl-k">function</span><span class="pl-k">*</span> <span
            class="pl-en">gen</span>(<span class="pl-smi">x</span>){
  <span class="pl-k">var</span> y <span class="pl-k">=</span> <span class="pl-k">yield</span> x <span
                class="pl-k">+</span> <span class="pl-c1">2</span>;
  <span class="pl-k">return</span> y;
}

<span class="pl-k">var</span> g <span class="pl-k">=</span> <span class="pl-en">gen</span>(<span class="pl-c1">1</span>);
<span class="pl-smi">g</span>.<span class="pl-c1">next</span>() <span class="pl-c"><span class="pl-c">//</span> { value: 3, done: false }</span>
<span class="pl-smi">g</span>.<span class="pl-c1">next</span>(<span class="pl-c1">2</span>) <span class="pl-c"><span
                class="pl-c">//</span> { value: 2, done: true }</span></pre>
    </div>
    <p>上面代码中，第一<code>next</code>方法的<code>value</code>属性，返回表达式<code>x + 2</code>的值<code>3</code>。第二个<code>next</code>方法带有参数<code>2</code>，这个参数可以传入
        Generator
        函数，作为上个阶段异步任务的返回结果，被函数体内的变量<code>y</code>接收。因此，这一步的<code>value</code>属性，返回的就是<code>2</code>（变量<code>y</code>的值）。
    </p>
    <p>Generator 函数内部还可以部署错误处理代码，捕获函数体外抛出的错误。</p>
    <div class="highlight highlight-source-js"><pre><span class="pl-k">function</span><span class="pl-k">*</span> <span
            class="pl-en">gen</span>(<span class="pl-smi">x</span>){
  <span class="pl-k">try</span> {
    <span class="pl-k">var</span> y <span class="pl-k">=</span> <span class="pl-k">yield</span> x <span
                class="pl-k">+</span> <span class="pl-c1">2</span>;
  } <span class="pl-k">catch</span> (e){
    <span class="pl-en">console</span>.<span class="pl-c1">log</span>(e);
  }
  <span class="pl-k">return</span> y;
}

<span class="pl-k">var</span> g <span class="pl-k">=</span> <span class="pl-en">gen</span>(<span class="pl-c1">1</span>);
<span class="pl-smi">g</span>.<span class="pl-c1">next</span>();
<span class="pl-smi">g</span>.<span class="pl-en">throw</span>(<span class="pl-s"><span class="pl-pds">'</span>出错了<span
                class="pl-pds">'</span></span>);
<span class="pl-c"><span class="pl-c">//</span> 出错了</span></pre>
    </div>
    <p>上面代码的最后一行，Generator 函数体外，使用指针对象的<code>throw</code>方法抛出的错误，可以被函数体内的<code>try...catch</code>代码块捕获。这意味着，出错的代码与处理错误的代码，实现了时间和空间上的分离，这对于异步编程无疑是很重要的。
    </p>
    <h3><a href="#异步任务的封装"  class="anchor" id="user-content-异步任务的封装">

    </a>异步任务的封装
    </h3>
    <p>下面看看如何使用 Generator 函数，执行一个真实的异步任务。</p>
    <div class="highlight highlight-source-js"><pre><span class="pl-k">var</span> fetch <span
            class="pl-k">=</span> <span class="pl-c1">require</span>(<span class="pl-s"><span class="pl-pds">'</span>node-fetch<span
            class="pl-pds">'</span></span>);

<span class="pl-k">function</span><span class="pl-k">*</span> <span class="pl-en">gen</span>(){
  <span class="pl-k">var</span> url <span class="pl-k">=</span> <span class="pl-s"><span class="pl-pds">'</span>https://api.github.com/users/github<span
                class="pl-pds">'</span></span>;
  <span class="pl-k">var</span> result <span class="pl-k">=</span> <span class="pl-k">yield</span> <span class="pl-en">fetch</span>(url);
  <span class="pl-en">console</span>.<span class="pl-c1">log</span>(<span class="pl-smi">result</span>.<span
                class="pl-smi">bio</span>);
}</pre>
    </div>
    <p>上面代码中，Generator 函数封装了一个异步操作，该操作先读取一个远程接口，然后从 JSON 格式的数据解析信息。就像前面说过的，这段代码非常像同步操作，除了加上了<code>yield</code>命令。</p>
    <p>执行这段代码的方法如下。</p>
    <div class="highlight highlight-source-js"><pre><span class="pl-k">var</span> g <span class="pl-k">=</span> <span
            class="pl-en">gen</span>();
<span class="pl-k">var</span> result <span class="pl-k">=</span> <span class="pl-smi">g</span>.<span
                class="pl-c1">next</span>();

<span class="pl-smi">result</span>.<span class="pl-c1">value</span>.<span class="pl-en">then</span>(<span class="pl-k">function</span>(<span
                class="pl-smi">data</span>){
  <span class="pl-k">return</span> <span class="pl-smi">data</span>.<span class="pl-en">json</span>();
}).<span class="pl-en">then</span>(<span class="pl-k">function</span>(<span class="pl-smi">data</span>){
  <span class="pl-smi">g</span>.<span class="pl-c1">next</span>(data);
});</pre>
    </div>
    <p>上面代码中，首先执行 Generator 函数，获取遍历器对象，然后使用<code>next</code>方法（第二行），执行异步任务的第一阶段。由于<code>Fetch</code>模块返回的是一个 Promise
        对象，因此要用<code>then</code>方法调用下一个<code>next</code>方法。</p>
    <p>可以看到，虽然 Generator 函数将异步操作表示得很简洁，但是流程管理却不方便（即何时执行第一阶段、何时执行第二阶段）。</p>
    <h2><a href="#thunk-函数"  class="anchor" id="user-content-thunk-函数">

    </a>Thunk 函数
    </h2>
    <p>Thunk 函数是自动执行 Generator 函数的一种方法。</p>
    <h3><a href="#参数的求值策略"  class="anchor" id="user-content-参数的求值策略">

    </a>参数的求值策略
    </h3>
    <p>Thunk 函数早在上个世纪 60 年代就诞生了。</p>
    <p>那时，编程语言刚刚起步，计算机学家还在研究，编译器怎么写比较好。一个争论的焦点是"求值策略"，即函数的参数到底应该何时求值。</p>
    <div class="highlight highlight-source-js"><pre><span class="pl-k">var</span> x <span class="pl-k">=</span> <span
            class="pl-c1">1</span>;

<span class="pl-k">function</span> <span class="pl-en">f</span>(<span class="pl-smi">m</span>){
  <span class="pl-k">return</span> m <span class="pl-k">*</span> <span class="pl-c1">2</span>;
}

<span class="pl-en">f</span>(x <span class="pl-k">+</span> <span class="pl-c1">5</span>)</pre>
    </div>
    <p>上面代码先定义函数<code>f</code>，然后向它传入表达式<code>x + 5</code>。请问，这个表达式应该何时求值？</p>
    <p>一种意见是"传值调用"（call by value），即在进入函数体之前，就计算<code>x + 5</code>的值（等于 6），再将这个值传入函数<code>f</code>。C 语言就采用这种策略。</p>
    <div class="highlight highlight-source-js"><pre><span class="pl-en">f</span>(x <span class="pl-k">+</span> <span
            class="pl-c1">5</span>)
<span class="pl-c"><span class="pl-c">//</span> 传值调用时，等同于</span>
<span class="pl-en">f</span>(<span class="pl-c1">6</span>)</pre>
    </div>
    <p>另一种意见是“传名调用”（call by name），即直接将表达式<code>x + 5</code>传入函数体，只在用到它的时候求值。Haskell 语言采用这种策略。</p>
    <div class="highlight highlight-source-js"><pre><span class="pl-en">f</span>(x <span class="pl-k">+</span> <span
            class="pl-c1">5</span>)
<span class="pl-c"><span class="pl-c">//</span> 传名调用时，等同于</span>
(x <span class="pl-k">+</span> <span class="pl-c1">5</span>) <span class="pl-k">*</span> <span
                class="pl-c1">2</span></pre>
    </div>
    <p>传值调用和传名调用，哪一种比较好？</p>
    <p>回答是各有利弊。传值调用比较简单，但是对参数求值的时候，实际上还没用到这个参数，有可能造成性能损失。</p>
    <div class="highlight highlight-source-js"><pre><span class="pl-k">function</span> <span
            class="pl-en">f</span>(<span class="pl-smi">a</span>, <span class="pl-smi">b</span>){
  <span class="pl-k">return</span> b;
}

<span class="pl-en">f</span>(<span class="pl-c1">3</span> <span class="pl-k">*</span> x <span
                class="pl-k">*</span> x <span class="pl-k">-</span> <span class="pl-c1">2</span> <span
                class="pl-k">*</span> x <span class="pl-k">-</span> <span class="pl-c1">1</span>, x);</pre>
    </div>
    <p>上面代码中，函数<code>f</code>的第一个参数是一个复杂的表达式，但是函数体内根本没用到。对这个参数求值，实际上是不必要的。因此，有一些计算机学家倾向于"传名调用"，即只在执行时求值。</p>
    <h3><a href="#thunk-函数的含义"  class="anchor" id="user-content-thunk-函数的含义">

    </a>Thunk 函数的含义
    </h3>
    <p>编译器的“传名调用”实现，往往是将参数放到一个临时函数之中，再将这个临时函数传入函数体。这个临时函数就叫做 Thunk 函数。</p>
    <div class="highlight highlight-source-js"><pre><span class="pl-k">function</span> <span
            class="pl-en">f</span>(<span class="pl-smi">m</span>) {
  <span class="pl-k">return</span> m <span class="pl-k">*</span> <span class="pl-c1">2</span>;
}

<span class="pl-en">f</span>(x <span class="pl-k">+</span> <span class="pl-c1">5</span>);

<span class="pl-c"><span class="pl-c">//</span> 等同于</span>

<span class="pl-k">var</span> <span class="pl-en">thunk</span> <span class="pl-k">=</span> <span
                class="pl-k">function</span> () {
  <span class="pl-k">return</span> x <span class="pl-k">+</span> <span class="pl-c1">5</span>;
};

<span class="pl-k">function</span> <span class="pl-en">f</span>(<span class="pl-smi">thunk</span>) {
  <span class="pl-k">return</span> <span class="pl-en">thunk</span>() <span class="pl-k">*</span> <span
                class="pl-c1">2</span>;
}</pre>
    </div>
    <p>上面代码中，函数 f 的参数<code>x + 5</code>被一个函数替换了。凡是用到原参数的地方，对<code>Thunk</code>函数求值即可。</p>
    <p>这就是 Thunk 函数的定义，它是“传名调用”的一种实现策略，用来替换某个表达式。</p>
    <h3><a href="#javascript-语言的-thunk-函数"  class="anchor" id="user-content-javascript-语言的-thunk-函数">

    </a>JavaScript 语言的 Thunk 函数
    </h3>
    <p>JavaScript 语言是传值调用，它的 Thunk 函数含义有所不同。在 JavaScript 语言中，Thunk 函数替换的不是表达式，而是多参数函数，将其替换成一个只接受回调函数作为参数的单参数函数。</p>
    <div class="highlight highlight-source-js"><pre><span class="pl-c"><span class="pl-c">//</span> 正常版本的readFile（多参数版本）</span>
<span class="pl-smi">fs</span>.<span class="pl-en">readFile</span>(fileName, callback);

<span class="pl-c"><span class="pl-c">//</span> Thunk版本的readFile（单参数版本）</span>
<span class="pl-k">var</span> <span class="pl-en">Thunk</span> <span class="pl-k">=</span> <span
                class="pl-k">function</span> (<span class="pl-smi">fileName</span>) {
  <span class="pl-k">return</span> <span class="pl-k">function</span> (<span class="pl-smi">callback</span>) {
    <span class="pl-k">return</span> <span class="pl-smi">fs</span>.<span class="pl-en">readFile</span>(fileName, callback);
  };
};

<span class="pl-k">var</span> readFileThunk <span class="pl-k">=</span> <span class="pl-en">Thunk</span>(fileName);
<span class="pl-en">readFileThunk</span>(callback);</pre>
    </div>
    <p>上面代码中，<code>fs</code>模块的<code>readFile</code>方法是一个多参数函数，两个参数分别为文件名和回调函数。经过转换器处理，它变成了一个单参数函数，只接受回调函数作为参数。这个单参数版本，就叫做
        Thunk 函数。</p>
    <p>任何函数，只要参数有回调函数，就能写成 Thunk 函数的形式。下面是一个简单的 Thunk 函数转换器。</p>
    <div class="highlight highlight-source-js"><pre><span class="pl-c"><span class="pl-c">//</span> ES5版本</span>
<span class="pl-k">var</span> <span class="pl-en">Thunk</span> <span class="pl-k">=</span> <span
                class="pl-k">function</span>(<span class="pl-smi">fn</span>){
  <span class="pl-k">return</span> <span class="pl-k">function</span> (){
    <span class="pl-k">var</span> args <span class="pl-k">=</span> <span class="pl-c1">Array</span>.<span class="pl-c1">prototype</span>.<span
                class="pl-smi">slice</span>.<span class="pl-c1">call</span>(<span class="pl-c1">arguments</span>);
    <span class="pl-k">return</span> <span class="pl-k">function</span> (<span class="pl-smi">callback</span>){
      <span class="pl-smi">args</span>.<span class="pl-c1">push</span>(callback);
      <span class="pl-k">return</span> <span class="pl-smi">fn</span>.<span class="pl-c1">apply</span>(<span
                class="pl-c1">this</span>, args);
    }
  };
};

<span class="pl-c"><span class="pl-c">//</span> ES6版本</span>
<span class="pl-k">const</span> <span class="pl-c1">Thunk</span> <span class="pl-k">=</span> <span
                class="pl-k">function</span>(<span class="pl-smi">fn</span>) {
  <span class="pl-k">return</span> <span class="pl-k">function</span> (<span class="pl-k">...</span><span class="pl-v">args</span>) {
    <span class="pl-k">return</span> <span class="pl-k">function</span> (<span class="pl-smi">callback</span>) {
      <span class="pl-k">return</span> <span class="pl-smi">fn</span>.<span class="pl-c1">call</span>(<span
                class="pl-c1">this</span>, <span class="pl-k">...</span>args, callback);
    }
  };
};</pre>
    </div>
    <p>使用上面的转换器，生成<code>fs.readFile</code>的 Thunk 函数。</p>
    <div class="highlight highlight-source-js"><pre><span class="pl-k">var</span> readFileThunk <span
            class="pl-k">=</span> <span class="pl-en">Thunk</span>(<span class="pl-smi">fs</span>.<span class="pl-smi">readFile</span>);
<span class="pl-en">readFileThunk</span>(fileA)(callback);</pre>
    </div>
    <p>下面是另一个完整的例子。</p>
    <div class="highlight highlight-source-js"><pre><span class="pl-k">function</span> <span
            class="pl-en">f</span>(<span class="pl-smi">a</span>, <span class="pl-smi">cb</span>) {
  <span class="pl-en">cb</span>(a);
}
<span class="pl-k">const</span> <span class="pl-c1">ft</span> <span class="pl-k">=</span> <span
                class="pl-en">Thunk</span>(f);

<span class="pl-en">ft</span>(<span class="pl-c1">1</span>)(<span class="pl-en">console</span>.<span
                class="pl-smi">log</span>) <span class="pl-c"><span class="pl-c">//</span> 1</span></pre>
    </div>
    <h3><a href="#thunkify-模块"  class="anchor" id="user-content-thunkify-模块">

    </a>Thunkify 模块
    </h3>
    <p>生产环境的转换器，建议使用 Thunkify 模块。</p>
    <p>首先是安装。</p>
    <div class="highlight highlight-source-shell">
        <pre>$ npm install thunkify</pre>
    </div>
    <p>使用方式如下。</p>
    <div class="highlight highlight-source-js"><pre><span class="pl-k">var</span> thunkify <span
            class="pl-k">=</span> <span class="pl-c1">require</span>(<span class="pl-s"><span class="pl-pds">'</span>thunkify<span
            class="pl-pds">'</span></span>);
<span class="pl-k">var</span> fs <span class="pl-k">=</span> <span class="pl-c1">require</span>(<span class="pl-s"><span
                class="pl-pds">'</span>fs<span class="pl-pds">'</span></span>);

<span class="pl-k">var</span> read <span class="pl-k">=</span> <span class="pl-en">thunkify</span>(<span class="pl-smi">fs</span>.<span
                class="pl-smi">readFile</span>);
<span class="pl-en">read</span>(<span class="pl-s"><span class="pl-pds">'</span>package.json<span
                class="pl-pds">'</span></span>)(<span class="pl-k">function</span>(<span
                class="pl-smi">err</span>, <span class="pl-smi">str</span>){
  <span class="pl-c"><span class="pl-c">//</span> ...</span>
});</pre>
    </div>
    <p>Thunkify 的源码与上一节那个简单的转换器非常像。</p>
    <div class="highlight highlight-source-js"><pre><span class="pl-k">function</span> <span
            class="pl-en">thunkify</span>(<span class="pl-smi">fn</span>) {
  <span class="pl-k">return</span> <span class="pl-k">function</span>() {
    <span class="pl-k">var</span> args <span class="pl-k">=</span> <span class="pl-k">new</span> <span class="pl-en">Array</span>(<span
                class="pl-c1">arguments</span>.<span class="pl-c1">length</span>);
    <span class="pl-k">var</span> ctx <span class="pl-k">=</span> <span class="pl-c1">this</span>;

    <span class="pl-k">for</span> (<span class="pl-k">var</span> i <span class="pl-k">=</span> <span
                class="pl-c1">0</span>; i <span class="pl-k">&lt;</span> <span class="pl-smi">args</span>.<span
                class="pl-c1">length</span>; <span class="pl-k">++</span>i) {
      args[i] <span class="pl-k">=</span> <span class="pl-c1">arguments</span>[i];
    }

    <span class="pl-k">return</span> <span class="pl-k">function</span> (<span class="pl-smi">done</span>) {
      <span class="pl-k">var</span> called;

      <span class="pl-smi">args</span>.<span class="pl-c1">push</span>(<span class="pl-k">function</span> () {
        <span class="pl-k">if</span> (called) <span class="pl-k">return</span>;
        called <span class="pl-k">=</span> <span class="pl-c1">true</span>;
        <span class="pl-smi">done</span>.<span class="pl-c1">apply</span>(<span class="pl-c1">null</span>, <span
                class="pl-c1">arguments</span>);
      });

      <span class="pl-k">try</span> {
        <span class="pl-smi">fn</span>.<span class="pl-c1">apply</span>(ctx, args);
      } <span class="pl-k">catch</span> (err) {
        <span class="pl-en">done</span>(err);
      }
    }
  }
};</pre>
    </div>
    <p>它的源码主要多了一个检查机制，变量<code>called</code>确保回调函数只运行一次。这样的设计与下文的 Generator 函数相关。请看下面的例子。</p>
    <div class="highlight highlight-source-js"><pre><span class="pl-k">function</span> <span
            class="pl-en">f</span>(<span class="pl-smi">a</span>, <span class="pl-smi">b</span>, <span class="pl-smi">callback</span>){
  <span class="pl-k">var</span> sum <span class="pl-k">=</span> a <span class="pl-k">+</span> b;
  <span class="pl-en">callback</span>(sum);
  <span class="pl-en">callback</span>(sum);
}

<span class="pl-k">var</span> ft <span class="pl-k">=</span> <span class="pl-en">thunkify</span>(f);
<span class="pl-k">var</span> print <span class="pl-k">=</span> <span class="pl-en">console</span>.<span class="pl-smi">log</span>.<span
                class="pl-en">bind</span>(<span class="pl-en">console</span>);
<span class="pl-en">ft</span>(<span class="pl-c1">1</span>, <span class="pl-c1">2</span>)(print);
<span class="pl-c"><span class="pl-c">//</span> 3</span></pre>
    </div>
    <p>上面代码中，由于<code>thunkify</code>只允许回调函数执行一次，所以只输出一行结果。</p>
    <h3><a href="#generator-函数的流程管理"  class="anchor" id="user-content-generator-函数的流程管理">

    </a>Generator 函数的流程管理
    </h3>
    <p>你可能会问， Thunk 函数有什么用？回答是以前确实没什么用，但是 ES6 有了 Generator 函数，Thunk 函数现在可以用于 Generator 函数的自动流程管理。</p>
    <p>Generator 函数可以自动执行。</p>
    <div class="highlight highlight-source-js"><pre><span class="pl-k">function</span><span class="pl-k">*</span> <span
            class="pl-en">gen</span>() {
  <span class="pl-c"><span class="pl-c">//</span> ...</span>
}

<span class="pl-k">var</span> g <span class="pl-k">=</span> <span class="pl-en">gen</span>();
<span class="pl-k">var</span> res <span class="pl-k">=</span> <span class="pl-smi">g</span>.<span
                class="pl-c1">next</span>();

<span class="pl-k">while</span>(<span class="pl-k">!</span><span class="pl-smi">res</span>.<span
                class="pl-smi">done</span>){
  <span class="pl-en">console</span>.<span class="pl-c1">log</span>(<span class="pl-smi">res</span>.<span class="pl-c1">value</span>);
  res <span class="pl-k">=</span> <span class="pl-smi">g</span>.<span class="pl-c1">next</span>();
}</pre>
    </div>
    <p>上面代码中，Generator 函数<code>gen</code>会自动执行完所有步骤。</p>
    <p>但是，这不适合异步操作。如果必须保证前一步执行完，才能执行后一步，上面的自动执行就不可行。这时，Thunk 函数就能派上用处。以读取文件为例。下面的 Generator 函数封装了两个异步操作。</p>
    <div class="highlight highlight-source-js"><pre><span class="pl-k">var</span> fs <span class="pl-k">=</span> <span
            class="pl-c1">require</span>(<span class="pl-s"><span class="pl-pds">'</span>fs<span class="pl-pds">'</span></span>);
<span class="pl-k">var</span> thunkify <span class="pl-k">=</span> <span class="pl-c1">require</span>(<span
                class="pl-s"><span class="pl-pds">'</span>thunkify<span class="pl-pds">'</span></span>);
<span class="pl-k">var</span> readFileThunk <span class="pl-k">=</span> <span class="pl-en">thunkify</span>(<span
                class="pl-smi">fs</span>.<span class="pl-smi">readFile</span>);

<span class="pl-k">var</span> <span class="pl-en">gen</span> <span class="pl-k">=</span> <span
                class="pl-k">function</span><span class="pl-k">*</span> (){
  <span class="pl-k">var</span> r1 <span class="pl-k">=</span> <span class="pl-k">yield</span> <span class="pl-en">readFileThunk</span>(<span
                class="pl-s"><span class="pl-pds">'</span>/etc/fstab<span class="pl-pds">'</span></span>);
  <span class="pl-en">console</span>.<span class="pl-c1">log</span>(<span class="pl-smi">r1</span>.<span class="pl-c1">toString</span>());
  <span class="pl-k">var</span> r2 <span class="pl-k">=</span> <span class="pl-k">yield</span> <span class="pl-en">readFileThunk</span>(<span
                class="pl-s"><span class="pl-pds">'</span>/etc/shells<span class="pl-pds">'</span></span>);
  <span class="pl-en">console</span>.<span class="pl-c1">log</span>(<span class="pl-smi">r2</span>.<span class="pl-c1">toString</span>());
};</pre>
    </div>
    <p>上面代码中，<code>yield</code>命令用于将程序的执行权移出 Generator 函数，那么就需要一种方法，将执行权再交还给 Generator 函数。</p>
    <p>这种方法就是 Thunk 函数，因为它可以在回调函数里，将执行权交还给 Generator 函数。为了便于理解，我们先看如何手动执行上面这个 Generator 函数。</p>
    <div class="highlight highlight-source-js"><pre><span class="pl-k">var</span> g <span class="pl-k">=</span> <span
            class="pl-en">gen</span>();

<span class="pl-k">var</span> r1 <span class="pl-k">=</span> <span class="pl-smi">g</span>.<span
                class="pl-c1">next</span>();
<span class="pl-smi">r1</span>.<span class="pl-c1">value</span>(<span class="pl-k">function</span> (<span
                class="pl-smi">err</span>, <span class="pl-smi">data</span>) {
  <span class="pl-k">if</span> (err) <span class="pl-k">throw</span> err;
  <span class="pl-k">var</span> r2 <span class="pl-k">=</span> <span class="pl-smi">g</span>.<span
                class="pl-c1">next</span>(data);
  <span class="pl-smi">r2</span>.<span class="pl-c1">value</span>(<span class="pl-k">function</span> (<span
                class="pl-smi">err</span>, <span class="pl-smi">data</span>) {
    <span class="pl-k">if</span> (err) <span class="pl-k">throw</span> err;
    <span class="pl-smi">g</span>.<span class="pl-c1">next</span>(data);
  });
});</pre>
    </div>
    <p>上面代码中，变量<code>g</code>是 Generator 函数的内部指针，表示目前执行到哪一步。<code>next</code>方法负责将指针移动到下一步，并返回该步的信息（<code>value</code>属性和<code>done</code>属性）。
    </p>
    <p>仔细查看上面的代码，可以发现 Generator 函数的执行过程，其实是将同一个回调函数，反复传入<code>next</code>方法的<code>value</code>属性。这使得我们可以用递归来自动完成这个过程。
    </p>
    <h3><a href="#thunk-函数的自动流程管理"  class="anchor" id="user-content-thunk-函数的自动流程管理">

    </a>Thunk 函数的自动流程管理
    </h3>
    <p>Thunk 函数真正的威力，在于可以自动执行 Generator 函数。下面就是一个基于 Thunk 函数的 Generator 执行器。</p>
    <div class="highlight highlight-source-js"><pre><span class="pl-k">function</span> <span
            class="pl-en">run</span>(<span class="pl-smi">fn</span>) {
  <span class="pl-k">var</span> gen <span class="pl-k">=</span> <span class="pl-en">fn</span>();

  <span class="pl-k">function</span> <span class="pl-en">next</span>(<span class="pl-smi">err</span>, <span
                class="pl-smi">data</span>) {
    <span class="pl-k">var</span> result <span class="pl-k">=</span> <span class="pl-smi">gen</span>.<span
                class="pl-c1">next</span>(data);
    <span class="pl-k">if</span> (<span class="pl-smi">result</span>.<span class="pl-smi">done</span>) <span
                class="pl-k">return</span>;
    <span class="pl-smi">result</span>.<span class="pl-c1">value</span>(next);
  }

  <span class="pl-en">next</span>();
}

<span class="pl-k">function</span><span class="pl-k">*</span> <span class="pl-en">g</span>() {
  <span class="pl-c"><span class="pl-c">//</span> ...</span>
}

<span class="pl-en">run</span>(g);</pre>
    </div>
    <p>上面代码的<code>run</code>函数，就是一个 Generator 函数的自动执行器。内部的<code>next</code>函数就是 Thunk 的回调函数。<code>next</code>函数先将指针移到
        Generator 函数的下一步（<code>gen.next</code>方法），然后判断 Generator
        函数是否结束（<code>result.done</code>属性），如果没结束，就将<code>next</code>函数再传入 Thunk 函数（<code>result.value</code>属性），否则就直接退出。
    </p>
    <p>有了这个执行器，执行 Generator 函数方便多了。不管内部有多少个异步操作，直接把 Generator 函数传入<code>run</code>函数即可。当然，前提是每一个异步操作，都要是 Thunk
        函数，也就是说，跟在<code>yield</code>命令后面的必须是 Thunk 函数。</p>
    <div class="highlight highlight-source-js"><pre><span class="pl-k">var</span> <span class="pl-en">g</span> <span
            class="pl-k">=</span> <span class="pl-k">function</span><span class="pl-k">*</span> (){
  <span class="pl-k">var</span> f1 <span class="pl-k">=</span> <span class="pl-k">yield</span> <span class="pl-en">readFileThunk</span>(<span
                class="pl-s"><span class="pl-pds">'</span>fileA<span class="pl-pds">'</span></span>);
  <span class="pl-k">var</span> f2 <span class="pl-k">=</span> <span class="pl-k">yield</span> <span class="pl-en">readFileThunk</span>(<span
                class="pl-s"><span class="pl-pds">'</span>fileB<span class="pl-pds">'</span></span>);
  <span class="pl-c"><span class="pl-c">//</span> ...</span>
  <span class="pl-k">var</span> fn <span class="pl-k">=</span> <span class="pl-k">yield</span> <span class="pl-en">readFileThunk</span>(<span
                class="pl-s"><span class="pl-pds">'</span>fileN<span class="pl-pds">'</span></span>);
};

<span class="pl-en">run</span>(g);</pre>
    </div>
    <p>上面代码中，函数<code>g</code>封装了<code>n</code>个异步的读取文件操作，只要执行<code>run</code>函数，这些操作就会自动完成。这样一来，异步操作不仅可以写得像同步操作，而且一行代码就可以执行。
    </p>
    <p>Thunk 函数并不是 Generator 函数自动执行的唯一方案。因为自动执行的关键是，必须有一种机制，自动控制 Generator 函数的流程，接收和交还程序的执行权。回调函数可以做到这一点，Promise
        对象也可以做到这一点。</p>
    <h2><a href="#co-模块"  class="anchor" id="user-content-co-模块">

    </a>co 模块
    </h2>
    <h3><a href="#基本用法"  class="anchor" id="user-content-基本用法">

    </a>基本用法
    </h3>
    <p><a href="https://github.com/tj/co">co 模块</a>是著名程序员 TJ Holowaychuk 于 2013 年 6 月发布的一个小工具，用于 Generator 函数的自动执行。</p>
    <p>下面是一个 Generator 函数，用于依次读取两个文件。</p>
    <div class="highlight highlight-source-js"><pre><span class="pl-k">var</span> <span class="pl-en">gen</span> <span
            class="pl-k">=</span> <span class="pl-k">function</span><span class="pl-k">*</span> () {
  <span class="pl-k">var</span> f1 <span class="pl-k">=</span> <span class="pl-k">yield</span> <span class="pl-en">readFile</span>(<span
                class="pl-s"><span class="pl-pds">'</span>/etc/fstab<span class="pl-pds">'</span></span>);
  <span class="pl-k">var</span> f2 <span class="pl-k">=</span> <span class="pl-k">yield</span> <span class="pl-en">readFile</span>(<span
                class="pl-s"><span class="pl-pds">'</span>/etc/shells<span class="pl-pds">'</span></span>);
  <span class="pl-en">console</span>.<span class="pl-c1">log</span>(<span class="pl-smi">f1</span>.<span class="pl-c1">toString</span>());
  <span class="pl-en">console</span>.<span class="pl-c1">log</span>(<span class="pl-smi">f2</span>.<span class="pl-c1">toString</span>());
};</pre>
    </div>
    <p>co 模块可以让你不用编写 Generator 函数的执行器。</p>
    <div class="highlight highlight-source-js"><pre><span class="pl-k">var</span> co <span class="pl-k">=</span> <span
            class="pl-c1">require</span>(<span class="pl-s"><span class="pl-pds">'</span>co<span class="pl-pds">'</span></span>);
<span class="pl-en">co</span>(gen);</pre>
    </div>
    <p>上面代码中，Generator 函数只要传入<code>co</code>函数，就会自动执行。</p>
    <p><code>co</code>函数返回一个<code>Promise</code>对象，因此可以用<code>then</code>方法添加回调函数。</p>
    <div class="highlight highlight-source-js"><pre><span class="pl-en">co</span>(gen).<span
            class="pl-en">then</span>(<span class="pl-k">function</span> (){
  <span class="pl-en">console</span>.<span class="pl-c1">log</span>(<span class="pl-s"><span class="pl-pds">'</span>Generator 函数执行完成<span
                class="pl-pds">'</span></span>);
});</pre>
    </div>
    <p>上面代码中，等到 Generator 函数执行结束，就会输出一行提示。</p>
    <h3><a href="#co-模块的原理"  class="anchor" id="user-content-co-模块的原理">

    </a>co 模块的原理
    </h3>
    <p>为什么 co 可以自动执行 Generator 函数？</p>
    <p>前面说过，Generator 就是一个异步操作的容器。它的自动执行需要一种机制，当异步操作有了结果，能够自动交回执行权。</p>
    <p>两种方法可以做到这一点。</p>
    <p>（1）回调函数。将异步操作包装成 Thunk 函数，在回调函数里面交回执行权。</p>
    <p>（2）Promise 对象。将异步操作包装成 Promise 对象，用<code>then</code>方法交回执行权。</p>
    <p>co 模块其实就是将两种自动执行器（Thunk 函数和 Promise 对象），包装成一个模块。使用 co 的前提条件是，Generator 函数的<code>yield</code>命令后面，只能是 Thunk 函数或
        Promise 对象。如果数组或对象的成员，全部都是 Promise 对象，也可以使用 co，详见后文的例子。</p>
    <p>上一节已经介绍了基于 Thunk 函数的自动执行器。下面来看，基于 Promise 对象的自动执行器。这是理解 co 模块必须的。</p>
    <h3><a href="#基于-promise-对象的自动执行"  class="anchor" id="user-content-基于-promise-对象的自动执行">

    </a>基于 Promise 对象的自动执行
    </h3>
    <p>还是沿用上面的例子。首先，把<code>fs</code>模块的<code>readFile</code>方法包装成一个 Promise 对象。</p>
    <div class="highlight highlight-source-js"><pre><span class="pl-k">var</span> fs <span class="pl-k">=</span> <span
            class="pl-c1">require</span>(<span class="pl-s"><span class="pl-pds">'</span>fs<span class="pl-pds">'</span></span>);

<span class="pl-k">var</span> <span class="pl-en">readFile</span> <span class="pl-k">=</span> <span class="pl-k">function</span> (<span
                class="pl-smi">fileName</span>){
  <span class="pl-k">return</span> <span class="pl-k">new</span> <span class="pl-en">Promise</span>(<span class="pl-k">function</span> (<span
                class="pl-smi">resolve</span>, <span class="pl-smi">reject</span>){
    <span class="pl-smi">fs</span>.<span class="pl-en">readFile</span>(fileName, <span
                class="pl-k">function</span>(<span class="pl-smi">error</span>, <span class="pl-smi">data</span>){
      <span class="pl-k">if</span> (error) <span class="pl-k">return</span> <span class="pl-en">reject</span>(error);
      <span class="pl-en">resolve</span>(data);
    });
  });
};

<span class="pl-k">var</span> <span class="pl-en">gen</span> <span class="pl-k">=</span> <span
                class="pl-k">function</span><span class="pl-k">*</span> (){
  <span class="pl-k">var</span> f1 <span class="pl-k">=</span> <span class="pl-k">yield</span> <span class="pl-en">readFile</span>(<span
                class="pl-s"><span class="pl-pds">'</span>/etc/fstab<span class="pl-pds">'</span></span>);
  <span class="pl-k">var</span> f2 <span class="pl-k">=</span> <span class="pl-k">yield</span> <span class="pl-en">readFile</span>(<span
                class="pl-s"><span class="pl-pds">'</span>/etc/shells<span class="pl-pds">'</span></span>);
  <span class="pl-en">console</span>.<span class="pl-c1">log</span>(<span class="pl-smi">f1</span>.<span class="pl-c1">toString</span>());
  <span class="pl-en">console</span>.<span class="pl-c1">log</span>(<span class="pl-smi">f2</span>.<span class="pl-c1">toString</span>());
};</pre>
    </div>
    <p>然后，手动执行上面的 Generator 函数。</p>
    <div class="highlight highlight-source-js"><pre><span class="pl-k">var</span> g <span class="pl-k">=</span> <span
            class="pl-en">gen</span>();

<span class="pl-smi">g</span>.<span class="pl-c1">next</span>().<span class="pl-c1">value</span>.<span class="pl-en">then</span>(<span
                class="pl-k">function</span>(<span class="pl-smi">data</span>){
  <span class="pl-smi">g</span>.<span class="pl-c1">next</span>(data).<span class="pl-c1">value</span>.<span
                class="pl-en">then</span>(<span class="pl-k">function</span>(<span class="pl-smi">data</span>){
    <span class="pl-smi">g</span>.<span class="pl-c1">next</span>(data);
  });
});</pre>
    </div>
    <p>手动执行其实就是用<code>then</code>方法，层层添加回调函数。理解了这一点，就可以写出一个自动执行器。</p>
    <div class="highlight highlight-source-js"><pre><span class="pl-k">function</span> <span
            class="pl-en">run</span>(<span class="pl-smi">gen</span>){
  <span class="pl-k">var</span> g <span class="pl-k">=</span> <span class="pl-en">gen</span>();

  <span class="pl-k">function</span> <span class="pl-en">next</span>(<span class="pl-smi">data</span>){
    <span class="pl-k">var</span> result <span class="pl-k">=</span> <span class="pl-smi">g</span>.<span class="pl-c1">next</span>(data);
    <span class="pl-k">if</span> (<span class="pl-smi">result</span>.<span class="pl-smi">done</span>) <span
                class="pl-k">return</span> <span class="pl-smi">result</span>.<span class="pl-c1">value</span>;
    <span class="pl-smi">result</span>.<span class="pl-c1">value</span>.<span class="pl-en">then</span>(<span
                class="pl-k">function</span>(<span class="pl-smi">data</span>){
      <span class="pl-en">next</span>(data);
    });
  }

  <span class="pl-en">next</span>();
}

<span class="pl-en">run</span>(gen);</pre>
    </div>
    <p>上面代码中，只要 Generator 函数还没执行到最后一步，<code>next</code>函数就调用自身，以此实现自动执行。</p>
    <h3><a href="#co-模块的源码"  class="anchor" id="user-content-co-模块的源码">

    </a>co 模块的源码
    </h3>
    <p>co 就是上面那个自动执行器的扩展，它的源码只有几十行，非常简单。</p>
    <p>首先，co 函数接受 Generator 函数作为参数，返回一个 Promise 对象。</p>
    <div class="highlight highlight-source-js"><pre><span class="pl-k">function</span> <span
            class="pl-en">co</span>(<span class="pl-smi">gen</span>) {
  <span class="pl-k">var</span> ctx <span class="pl-k">=</span> <span class="pl-c1">this</span>;

  <span class="pl-k">return</span> <span class="pl-k">new</span> <span class="pl-en">Promise</span>(<span class="pl-k">function</span>(<span
                class="pl-smi">resolve</span>, <span class="pl-smi">reject</span>) {
  });
}</pre>
    </div>
    <p>在返回的 Promise 对象里面，co 先检查参数<code>gen</code>是否为 Generator 函数。如果是，就执行该函数，得到一个内部指针对象；如果不是就返回，并将 Promise 对象的状态改为<code>resolved</code>。
    </p>
    <div class="highlight highlight-source-js"><pre><span class="pl-k">function</span> <span
            class="pl-en">co</span>(<span class="pl-smi">gen</span>) {
  <span class="pl-k">var</span> ctx <span class="pl-k">=</span> <span class="pl-c1">this</span>;

  <span class="pl-k">return</span> <span class="pl-k">new</span> <span class="pl-en">Promise</span>(<span class="pl-k">function</span>(<span
                class="pl-smi">resolve</span>, <span class="pl-smi">reject</span>) {
    <span class="pl-k">if</span> (<span class="pl-k">typeof</span> gen <span class="pl-k">===</span> <span class="pl-s"><span
                class="pl-pds">'</span>function<span class="pl-pds">'</span></span>) gen <span
                class="pl-k">=</span> <span class="pl-smi">gen</span>.<span class="pl-c1">call</span>(ctx);
    <span class="pl-k">if</span> (<span class="pl-k">!</span>gen <span class="pl-k">||</span> <span
                class="pl-k">typeof</span> <span class="pl-smi">gen</span>.<span class="pl-c1">next</span> <span
                class="pl-k">!==</span> <span class="pl-s"><span class="pl-pds">'</span>function<span
                class="pl-pds">'</span></span>) <span class="pl-k">return</span> <span class="pl-en">resolve</span>(gen);
  });
}</pre>
    </div>
    <p>接着，co 将 Generator 函数的内部指针对象的<code>next</code>方法，包装成<code>onFulfilled</code>函数。这主要是为了能够捕捉抛出的错误。</p>
    <div class="highlight highlight-source-js"><pre><span class="pl-k">function</span> <span
            class="pl-en">co</span>(<span class="pl-smi">gen</span>) {
  <span class="pl-k">var</span> ctx <span class="pl-k">=</span> <span class="pl-c1">this</span>;

  <span class="pl-k">return</span> <span class="pl-k">new</span> <span class="pl-en">Promise</span>(<span class="pl-k">function</span>(<span
                class="pl-smi">resolve</span>, <span class="pl-smi">reject</span>) {
    <span class="pl-k">if</span> (<span class="pl-k">typeof</span> gen <span class="pl-k">===</span> <span class="pl-s"><span
                class="pl-pds">'</span>function<span class="pl-pds">'</span></span>) gen <span
                class="pl-k">=</span> <span class="pl-smi">gen</span>.<span class="pl-c1">call</span>(ctx);
    <span class="pl-k">if</span> (<span class="pl-k">!</span>gen <span class="pl-k">||</span> <span
                class="pl-k">typeof</span> <span class="pl-smi">gen</span>.<span class="pl-c1">next</span> <span
                class="pl-k">!==</span> <span class="pl-s"><span class="pl-pds">'</span>function<span
                class="pl-pds">'</span></span>) <span class="pl-k">return</span> <span class="pl-en">resolve</span>(gen);

    <span class="pl-en">onFulfilled</span>();
    <span class="pl-k">function</span> <span class="pl-en">onFulfilled</span>(<span class="pl-smi">res</span>) {
      <span class="pl-k">var</span> ret;
      <span class="pl-k">try</span> {
        ret <span class="pl-k">=</span> <span class="pl-smi">gen</span>.<span class="pl-c1">next</span>(res);
      } <span class="pl-k">catch</span> (e) {
        <span class="pl-k">return</span> <span class="pl-en">reject</span>(e);
      }
      <span class="pl-en">next</span>(ret);
    }
  });
}</pre>
    </div>
    <p>最后，就是关键的<code>next</code>函数，它会反复调用自身。</p>
    <div class="highlight highlight-source-js"><pre><span class="pl-k">function</span> <span
            class="pl-en">next</span>(<span class="pl-smi">ret</span>) {
  <span class="pl-k">if</span> (<span class="pl-smi">ret</span>.<span class="pl-smi">done</span>) <span class="pl-k">return</span> <span
                class="pl-en">resolve</span>(<span class="pl-smi">ret</span>.<span class="pl-c1">value</span>);
  <span class="pl-k">var</span> value <span class="pl-k">=</span> <span class="pl-smi">toPromise</span>.<span
                class="pl-c1">call</span>(ctx, <span class="pl-smi">ret</span>.<span class="pl-c1">value</span>);
  <span class="pl-k">if</span> (value <span class="pl-k">&amp;&amp;</span> <span class="pl-en">isPromise</span>(value)) <span
                class="pl-k">return</span> <span class="pl-smi">value</span>.<span class="pl-en">then</span>(onFulfilled, onRejected);
  <span class="pl-k">return</span> <span class="pl-en">onRejected</span>(
    <span class="pl-k">new</span> <span class="pl-en">TypeError</span>(
      <span class="pl-s"><span class="pl-pds">'</span>You may only yield a function, promise, generator, array, or object, <span
              class="pl-pds">'</span></span>
      <span class="pl-k">+</span> <span class="pl-s"><span class="pl-pds">'</span>but the following object was passed: "<span
                class="pl-pds">'</span></span>
      <span class="pl-k">+</span> <span class="pl-c1">String</span>(<span class="pl-smi">ret</span>.<span class="pl-c1">value</span>)
      <span class="pl-k">+</span> <span class="pl-s"><span class="pl-pds">'</span>"<span class="pl-pds">'</span></span>
    )
  );
}</pre>
    </div>
    <p>上面代码中，<code>next</code>函数的内部代码，一共只有四行命令。</p>
    <p>第一行，检查当前是否为 Generator 函数的最后一步，如果是就返回。</p>
    <p>第二行，确保每一步的返回值，是 Promise 对象。</p>
    <p>第三行，使用<code>then</code>方法，为返回值加上回调函数，然后通过<code>onFulfilled</code>函数再次调用<code>next</code>函数。</p>
    <p>第四行，在参数不符合要求的情况下（参数非 Thunk 函数和 Promise 对象），将 Promise 对象的状态改为<code>rejected</code>，从而终止执行。</p>
    <h3><a href="#处理并发的异步操作"  class="anchor" id="user-content-处理并发的异步操作">

    </a>处理并发的异步操作
    </h3>
    <p>co 支持并发的异步操作，即允许某些操作同时进行，等到它们全部完成，才进行下一步。</p>
    <p>这时，要把并发的操作都放在数组或对象里面，跟在<code>yield</code>语句后面。</p>
    <div class="highlight highlight-source-js"><pre><span class="pl-c"><span class="pl-c">//</span> 数组的写法</span>
<span class="pl-en">co</span>(<span class="pl-k">function</span><span class="pl-k">*</span> () {
  <span class="pl-k">var</span> res <span class="pl-k">=</span> <span class="pl-k">yield</span> [
    <span class="pl-c1">Promise</span>.<span class="pl-en">resolve</span>(<span class="pl-c1">1</span>),
    <span class="pl-c1">Promise</span>.<span class="pl-en">resolve</span>(<span class="pl-c1">2</span>)
  ];
  <span class="pl-en">console</span>.<span class="pl-c1">log</span>(res);
}).<span class="pl-en">catch</span>(onerror);

<span class="pl-c"><span class="pl-c">//</span> 对象的写法</span>
<span class="pl-en">co</span>(<span class="pl-k">function</span><span class="pl-k">*</span> () {
  <span class="pl-k">var</span> res <span class="pl-k">=</span> <span class="pl-k">yield</span> {
    <span class="pl-c1">1</span><span class="pl-k">:</span> <span class="pl-c1">Promise</span>.<span class="pl-en">resolve</span>(<span
                class="pl-c1">1</span>),
    <span class="pl-c1">2</span><span class="pl-k">:</span> <span class="pl-c1">Promise</span>.<span class="pl-en">resolve</span>(<span
                class="pl-c1">2</span>),
  };
  <span class="pl-en">console</span>.<span class="pl-c1">log</span>(res);
}).<span class="pl-en">catch</span>(onerror);</pre>
    </div>
    <p>下面是另一个例子。</p>
    <div class="highlight highlight-source-js"><pre><span class="pl-en">co</span>(<span
            class="pl-k">function</span><span class="pl-k">*</span> () {
  <span class="pl-k">var</span> values <span class="pl-k">=</span> [n1, n2, n3];
  <span class="pl-k">yield</span> <span class="pl-smi">values</span>.<span class="pl-en">map</span>(somethingAsync);
});

<span class="pl-k">function</span><span class="pl-k">*</span> <span class="pl-en">somethingAsync</span>(<span
                class="pl-smi">x</span>) {
  <span class="pl-c"><span class="pl-c">//</span> do something async</span>
  <span class="pl-k">return</span> y
}</pre>
    </div>
    <p>上面的代码允许并发三个<code>somethingAsync</code>异步操作，等到它们全部完成，才会进行下一步。</p>
    <h3><a href="#实例处理-stream"  class="anchor" id="user-content-实例处理-stream">

    </a>实例：处理 Stream
    </h3>
    <p>Node 提供 Stream 模式读写数据，特点是一次只处理数据的一部分，数据分成一块块依次处理，就好像“数据流”一样。这对于处理大规模数据非常有利。Stream 模式使用 EventEmitter
        API，会释放三个事件。</p>
    <ul>
        <li><code>data</code>事件：下一块数据块已经准备好了。</li>
        <li><code>end</code>事件：整个“数据流”处理“完了。</li>
        <li><code>error</code>事件：发生错误。</li>
    </ul>
    <p>使用<code>Promise.race()</code>函数，可以判断这三个事件之中哪一个最先发生，只有当<code>data</code>事件最先发生时，才进入下一个数据块的处理。从而，我们可以通过一个<code>while</code>循环，完成所有数据的读取。
    </p>
    <div class="highlight highlight-source-js"><pre><span class="pl-k">const</span> <span class="pl-c1">co</span> <span
            class="pl-k">=</span> <span class="pl-c1">require</span>(<span class="pl-s"><span
            class="pl-pds">'</span>co<span class="pl-pds">'</span></span>);
<span class="pl-k">const</span> <span class="pl-c1">fs</span> <span class="pl-k">=</span> <span
                class="pl-c1">require</span>(<span class="pl-s"><span class="pl-pds">'</span>fs<span
                class="pl-pds">'</span></span>);

<span class="pl-k">const</span> <span class="pl-c1">stream</span> <span class="pl-k">=</span> <span
                class="pl-smi">fs</span>.<span class="pl-en">createReadStream</span>(<span class="pl-s"><span
                class="pl-pds">'</span>./les_miserables.txt<span class="pl-pds">'</span></span>);
<span class="pl-k">let</span> valjeanCount <span class="pl-k">=</span> <span class="pl-c1">0</span>;

<span class="pl-en">co</span>(<span class="pl-k">function</span><span class="pl-k">*</span>() {
  <span class="pl-k">while</span>(<span class="pl-c1">true</span>) {
    <span class="pl-k">const</span> <span class="pl-c1">res</span> <span class="pl-k">=</span> <span
                class="pl-k">yield</span> <span class="pl-c1">Promise</span>.<span class="pl-en">race</span>([
      <span class="pl-k">new</span> <span class="pl-en">Promise</span>(<span class="pl-smi">resolve</span> <span
                class="pl-k">=&gt;</span> <span class="pl-smi">stream</span>.<span class="pl-en">once</span>(<span
                class="pl-s"><span class="pl-pds">'</span>data<span class="pl-pds">'</span></span>, resolve)),
      <span class="pl-k">new</span> <span class="pl-en">Promise</span>(<span class="pl-smi">resolve</span> <span
                class="pl-k">=&gt;</span> <span class="pl-smi">stream</span>.<span class="pl-en">once</span>(<span
                class="pl-s"><span class="pl-pds">'</span>end<span class="pl-pds">'</span></span>, resolve)),
      <span class="pl-k">new</span> <span class="pl-en">Promise</span>((<span class="pl-smi">resolve</span>, <span
                class="pl-smi">reject</span>) <span class="pl-k">=&gt;</span> <span class="pl-smi">stream</span>.<span
                class="pl-en">once</span>(<span class="pl-s"><span class="pl-pds">'</span>error<span
                class="pl-pds">'</span></span>, reject))
    ]);
    <span class="pl-k">if</span> (<span class="pl-k">!</span>res) {
      <span class="pl-k">break</span>;
    }
    <span class="pl-smi">stream</span>.<span class="pl-en">removeAllListeners</span>(<span class="pl-s"><span
                class="pl-pds">'</span>data<span class="pl-pds">'</span></span>);
    <span class="pl-smi">stream</span>.<span class="pl-en">removeAllListeners</span>(<span class="pl-s"><span
                class="pl-pds">'</span>end<span class="pl-pds">'</span></span>);
    <span class="pl-smi">stream</span>.<span class="pl-en">removeAllListeners</span>(<span class="pl-s"><span
                class="pl-pds">'</span>error<span class="pl-pds">'</span></span>);
    valjeanCount <span class="pl-k">+=</span> (<span class="pl-smi">res</span>.<span
                class="pl-c1">toString</span>().<span class="pl-c1">match</span>(<span class="pl-sr"><span
                class="pl-pds">/</span>valjean<span class="pl-pds">/</span>ig</span>) <span
                class="pl-k">||</span> []).<span class="pl-c1">length</span>;
  }
  <span class="pl-en">console</span>.<span class="pl-c1">log</span>(<span class="pl-s"><span class="pl-pds">'</span>count:<span
                class="pl-pds">'</span></span>, valjeanCount); <span class="pl-c"><span class="pl-c">//</span> count: 1120</span>
});</pre>
    </div>
    <p>上面代码采用 Stream 模式读取《悲惨世界》的文本文件，对于每个数据块都使用<code>stream.once</code>方法，在<code>data</code>、<code>end</code>、<code>error</code>三个事件上添加一次性回调函数。变量<code>res</code>只有在<code>data</code>事件发生时才有值，然后累加每个数据块之中<code>valjean</code>这个词出现的次数。
    </p>
</div>
</body>
</html>
